// SPDX-License-Identifier: GPL-2.0
#ifndef _NF_ALG_DEBUG_H
#define _NF_ALG_DEBUG_H

#include <stdarg.h>
#include <net/netfilter/nf_nat.h>
#include <net/netfilter/nf_conntrack_expect.h>

#define ALG_SIP  1
#define ALG_FTP  2
#define ALG_TFTP 3
#define ALG_PPTP 4
#define ALG_H323 5

#define PROTO_SIP  "SIP"
#define PROTO_FTP  "FTP"
#define PROTO_TFTP "TFTP"
#define PROTO_PPTP "PPTP"
#define PROTO_H323 "H323"

#ifdef CONFIG_NF_CT_ALG_DEBUG

static const char *proto_str(int proto)
{
	switch (proto) {
	case ALG_SIP:
		return PROTO_SIP;
	case ALG_FTP:
		return PROTO_FTP;
	case ALG_TFTP:
		return PROTO_TFTP;
	case ALG_PPTP:
		return PROTO_PPTP;
	case ALG_H323:
		return PROTO_H323;
	default:
		return "";
	}
}

static const char *proto_msg(int proto)
{
	if (proto == ALG_H323)
		return "Q931";

	if (proto == ALG_PPTP)
		return "GRE";

	return "";
}

static uint16_t proto_key(struct nf_nat_range2 *range, int proto)
{
	if (proto == ALG_PPTP)
		return ntohs(range->min_proto.gre.key);

	return ntohs(range->min_proto.all);
}

void alg_debug(const char *proto, const char *fmt, ...);
void alg_debug_exp(const char *proto,
	const char *exp_type, struct nf_conn *ct,
	struct nf_conntrack_expect *exp);
void alg_debug_result(const char *proto,
	const char *msg_type, uint32_t result);
void alg_debug_recvpkg(const char *proto,
	const char *msg_type, struct nf_conn *ct,
	enum ip_conntrack_info ctinfo);
void alg_debug_mangle_data(const char *proto,
	const char *ptr, uint32_t len, const char *key, bool flag);
void alg_debug_mangle_addr_port(const char *proto,
	uint16_t l3num, union nf_inet_addr *addr,
	uint16_t port, const char *key, bool flag);

#define ALG_DBG(proto, fmt, ...) \
	alg_debug(proto, fmt, ##__VA_ARGS__)
#define ALG_DBG_EXPECT(proto, exp_type, ct, exp, cond) \
	do { if (cond) alg_debug_exp(proto, exp_type, ct, exp); } while (0)
#define ALG_DBG_RESULT(proto, msg_type, result) \
	alg_debug_result(proto, msg_type, result)
#define ALG_DBG_RCVPKT(proto, msg_type, ct, ctinfo) \
	alg_debug_recvpkg(proto, msg_type, ct, ctinfo)
#define ALG_DBG_MDATA(proto, ptr, len, key, flag) \
	alg_debug_mangle_data(proto, ptr, len, key, flag)
#define ALG_DBG_MADDR(proto, l3num, addr, port, key, flag) \
	alg_debug_mangle_addr_port(proto, l3num, addr, port, key, flag)

static void printk_range_v4(struct nf_nat_range2 *range, const char *type, int proto)
{
	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
		ALG_DBG(proto_str(proto), "%s the matched %s message to [%pI4:%hu]",
			type, proto_msg(proto), &range->min_addr.ip, proto_key(range, proto));
	else
		ALG_DBG(proto_str(proto), "%s the matched %s message to [%pI4]",
			type, proto_msg(proto), &range->min_addr.ip);
}

static void printk_range_v6(struct nf_nat_range2 *range, const char *type, int proto)
{
	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
		ALG_DBG(proto_str(proto), "%s the matched %s message to [[%pI6]:%hu]",
			type, proto_msg(proto), range->min_addr.all, proto_key(range, proto));
	else
		ALG_DBG(proto_str(proto), "%s the matched %s message to [%pI6]",
			type, proto_msg(proto), range->min_addr.all);
}

static inline void
printk_range(uint16_t l3num, struct nf_nat_range2 *range, const char *type, int proto)
{
	if (l3num == AF_INET)
		printk_range_v4(range, type, proto);
	else if (l3num == AF_INET6)
		printk_range_v6(range, type, proto);
}

static inline void printk_tuple(uint16_t l3num, struct nf_conntrack_tuple *tuple)
{
	const char *prefix = "Receive GRE message, remove other direction's expectation";

	if (l3num == AF_INET) {
		ALG_DBG(PROTO_PPTP, "%s [%pI4:%hu->%pI4:%hu]", prefix,
			&tuple->src.u3.ip, ntohs(tuple->src.u.gre.key),
			&tuple->dst.u3.ip, ntohs(tuple->dst.u.gre.key));
	} else if (l3num == AF_INET6) {
		ALG_DBG(PROTO_PPTP, "%s [[%pI6]:%hu->[%pI6]:%hu]", prefix,
			tuple->src.u3.all, ntohs(tuple->src.u.gre.key),
			tuple->dst.u3.all, ntohs(tuple->dst.u.gre.key));
	}
}

static inline void printk_exp(bool cond, uint16_t l3num, struct nf_conntrack_expect *exp)
{
	if (!cond || l3num != AF_INET)
		return;

	ALG_DBG(PROTO_PPTP, "Create GRE expect connection[%pI4:%hu -> %pI4:%hu]",
		&exp->tuple.src.u3.ip, ntohs(exp->tuple.src.u.gre.key),
		&exp->tuple.dst.u3.ip, ntohs(exp->tuple.dst.u.gre.key));
}

static inline void printk_nat(uint16_t l3num, union nf_inet_addr *saddr, uint16_t sport,
			      union nf_inet_addr *daddr, uint16_t dport, const char *key)
{
	ALG_DBG_MADDR(PROTO_H323, l3num, saddr, sport, key, true);
	ALG_DBG_MADDR(PROTO_H323, l3num, daddr, dport, key, false);
}

static inline void printk_mangle_err(const char *key, bool ori)
{
	if (ori) {
		ALG_DBG(PROTO_SIP, "Can not mangle %s of packet", key);
		return;
	}
	ALG_DBG(PROTO_SIP, "Can not mangle %s", key);
}

#else /* CONFIG_NF_CT_ALG_DEBUG */

static inline void alg_dbg_nothing(void) { }

#define ALG_DBG(proto, fmt, ...) alg_dbg_nothing()
#define ALG_DBG_EXPECT(proto, exp_type, ct, exp, cond) alg_dbg_nothing()
#define ALG_DBG_RESULT(proto, msg_type, result) alg_dbg_nothing()
#define ALG_DBG_RCVPKT(proto, msg_type, ct, ctinfo) alg_dbg_nothing()
#define ALG_DBG_MDATA(proto, ptr, len, key, flag) alg_dbg_nothing()
#define ALG_DBG_MADDR(proto, l3num, addr, port, key, flag) alg_dbg_nothing()

static inline void
printk_range(uint16_t l3num, struct nf_nat_range2 *range, const char *type, int proto)
{
}

static inline void printk_tuple(uint16_t l3num, struct nf_conntrack_tuple *tuple)
{
}

static inline void printk_exp(bool cond, uint16_t l3num, struct nf_conntrack_expect *exp)
{
}

static inline void printk_nat(uint16_t l3num, union nf_inet_addr *saddr, uint16_t sport,
			      union nf_inet_addr *daddr, uint16_t dport, const char *key)
{
}

static inline void printk_mangle_err(const char *key, bool ori)
{
}

#endif /* CONFIG_NF_CT_ALG_DEBUG */

#endif /* _NF_ALG_DEBUG_H */
